package com.bckj.fastboot.core.util;

import com.bckj.fastboot.core.lang.func.Func2;
import lombok.Getter;

import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

@SuppressWarnings("ALL")
public sealed interface Flow<T> permits Flow.Head, Flow.Node {

    static <T> Flow<T> of(Collection<T> source) {
        return new Head<>(source);
    }

    default void setPrev(Flow<?> prev) {
        throw new UnsupportedOperationException();
    }

    default <O> Flow<O> prev() {
        throw new UnsupportedOperationException();
    }

    default void setNext(Flow<?> next) {
        throw new UnsupportedOperationException();
    }

    default <O> Flow<O> next() {
        throw new UnsupportedOperationException();
    }

    default <O> Flow.Head<O> getHead() {
        throw new UnsupportedOperationException();
    }

    default Flow<T> peek(Consumer<T> consumer) {
        PeekNode<T> node = new PeekNode<>(getHead(), consumer);
        node.setPrev(this);
        this.setNext(node);
        return node;
    }

    default Flow<T> filter(Predicate<T> filter) {
        FilterNode<T> node = new FilterNode<>(getHead(), filter);
        node.setPrev(this);
        this.setNext(node);
        return node;
    }

    default <O> Flow<O> map(Function<T, O> mapper) {
        MapNode<O> node = new MapNode<>(getHead(), mapper);
        node.setPrev(this);
        this.setNext(node);
        return node;
    }

    default <O> Flow<O> flatMap(Function<T, Flow<O>> mapper) {
        FlatMapNode<O> node = new FlatMapNode<>(getHead(), mapper);
        node.setPrev(this);
        this.setNext(node);
        return node;
    }

    default <K, V, R> Flow<R> join(Map<K, V> target, Function<T, K> sourceKeyFunc, Function<V, K> targetKeyFunc, Func2<T, V, R> consumer) {
        Flow<R> node = new JoinNode<>(getHead(), target, sourceKeyFunc, targetKeyFunc, consumer);
        node.setPrev(this);
        this.setNext(node);
        return node;
    }

    default Flow.CollectionNode<T> collection() {
        Flow.CollectionNode<T> node = new CollectionNode<>(getHead());
        node.setPrev(this);
        return node;
    }

    default <R> Collection<R> get() {
        throw new UnsupportedOperationException();
    }

    final class Head<T> implements Flow<T> {

        private Flow<?> next;
        @Getter
        private final Collection<T> source;

        public Head(Collection<T> source) {
            this.source = source;
        }

        @Override
        public void setNext(Flow<?> next) {
            this.next = next;
        }

        @Override
        public <O> Flow<O> next() {
            return (Flow<O>) next;
        }

        @Override
        public <O> Flow.Head<O> getHead() {
            return (Flow.Head<O>) this;
        }

        @Override
        public <R> Collection<R> get() {
            return (Collection<R>) source;
        }
    }

    sealed class Node<T> implements Flow<T> {
        private Flow<?> prev;

        private Flow<?> next;
        private final Flow.Head<?> head;

        public Node(Flow.Head<?> head) {
            this.head = head;
        }

        @Override
        public void setPrev(Flow<?> prev) {
            this.prev = prev;
        }

        @Override
        public <O> Flow<O> prev() {
            return (Flow<O>) prev;
        }

        @Override
        public void setNext(Flow<?> next) {
            this.next = next;
        }

        @Override
        public <O> Flow<O> next() {
            return (Flow<O>) next;
        }

        @Override
        public <O> Head<O> getHead() {
            return (Flow.Head<O>) head;
        }
    }

    @Getter
    final class PeekNode<T> extends Node<T> {
        private final Consumer<T> consumer;

        public PeekNode(Flow.Head<?> head, Consumer<T> consumer) {
            super(head);
            this.consumer = consumer;
        }

    }

    @Getter
    final class FilterNode<T> extends Node<T> {
        private final Predicate filter;

        public FilterNode(Flow.Head<?> head, Predicate<?> filter) {
            super(head);
            this.filter = filter;
        }

    }

    @Getter
    final class MapNode<T> extends Node<T> {
        private final Function mapper;

        public MapNode(Flow.Head<?> head, Function<?, ?> mapper) {
            super(head);
            this.mapper = mapper;
        }

    }

    @Getter
    final class FlatMapNode<T> extends Node<T> {
        private final Function mapper;
        public FlatMapNode(Flow.Head<?> head, Function<?, ?> mapper) {
            super(head);
            this.mapper = mapper;
        }

    }

    @Getter
    final class JoinNode<T> extends Node<T> {
        private final Map<?, ?> target;
        private final Function sourceKeyFunc;
        private final Function targetKeyFunc;
        private final Func2 consumer;

        public JoinNode(Flow.Head<?> head, Map<?, ?> target, Function<?, ?> sourceKeyFunc, Function<?, ?> targetKeyFunc, Func2<?, ?, T> consumer) {
            super(head);
            this.target = target;
            this.sourceKeyFunc = sourceKeyFunc;
            this.targetKeyFunc = targetKeyFunc;
            this.consumer = consumer;
        }

    }

    final class CollectionNode<T> extends Node<T> {

        public CollectionNode(Flow.Head<?> head) {
            super(head);
        }

        @Override
        public void setNext(Flow<?> next) {
            throw new UnsupportedOperationException();
        }

        void collect(Collection<T> result, Flow<?> in, Object item) {
            Object r = item;
            for (Flow<?> node = in; node != null; node = node.next()) {
                if (node instanceof Flow.FilterNode<?> filterNode) {
                    boolean filter = filterNode.getFilter().test(r);
                    if (!filter) {
                        return;
                    }
                } else if (node instanceof Flow.MapNode<?> mapNode) {
                    Function mapper = mapNode.getMapper();
                    r =  mapper.apply(r);
                } else if (node instanceof Flow.FlatMapNode<?> flatMapNode) {
                    Function mapper = flatMapNode.getMapper();
                    Flow v = (Flow) mapper.apply(r);
                    var list = v.get();
                    for (Object subItem : list) {
                        collect(result, node.next(), subItem);
                    }
                    return;
                } else if (node instanceof Flow.JoinNode<?> joinNode) {
                    Map target = joinNode.getTarget();
                    Object key = joinNode.getSourceKeyFunc().apply(r);
                    Object v = target.get(key);
                    r = joinNode.getConsumer().call(r, v);
                }
            }
            result.add((T) r);
        }

        public List<T> toList() {
            List<T> result = new ArrayList<>();
            Flow.Head<?> head = getHead();
            for (Object item : head.getSource()) {
                collect(result, head.next(), item);
            }
            return result;
        }

        public Set<T> toSet() {
            Set<T> result = new HashSet<>();
            Flow.Head<?> head = getHead();
            for (Object item : head.getSource()) {
                collect(result, head.next(), item);
            }
            return result;
        }

        public <C extends Collection<T>> C supply(Supplier<C> collectionSupplier) {
            Collection<T> result = collectionSupplier.get();
            Flow.Head<?> head = getHead();
            for (Object item : head.getSource()) {
                collect(result, head.next(), item);
            }
            return (C) result;
        }
    }
}
